import { Injectable, OnApplicationBootstrap } from '@nestjs/common';
import {
  AudioSynthesisBatchDto,
  AudioSynthesisIndexEarbuds2EarbudsDto,
  Earbuds2EarbudsDto,
} from '@/module/audio-synthesis/audio-synthesis.dto';
import { InjectRepository } from '@nestjs/typeorm';
import { Between, In, LessThanOrEqual, Not, Repository } from 'typeorm';
import { BizFailedException } from '@/common/biz-failed/biz-failed.exception';
import { BizFailedCodeEnum } from '@/common/biz-failed/biz-failed.enum';
import { ConfigService } from '@nestjs/config';
import { FileService } from '@/module/file/file.service';
import path from 'path';
import fsExtra from 'fs-extra';
import { customAlphabet } from 'nanoid';
import {
  AudioSynthesisStatus,
  FileGroup,
  AUDIO_SYNTHESIS_FINALLY_STATUS_LIST,
  AUDIO_SYNTHESIS_COUNT_BY_PER_USER_PER_DAY,
  AUDIO_SYNTHESIS_FILE_CLEAN_DAYS,
  SimulationAlgoType,
} from 'common/utils/data';
import { exec } from 'child_process';
import { LoggerService } from '@/common/logger/logger.service';
import { EvaluationFileModel } from '@/module/evaluation-file/evaluation-file.model';
import { AudioSynthesisModel } from '@/module/audio-synthesis/audio-synthesis.model';
import dayjs from 'dayjs';
import { Transactional } from 'typeorm-transactional';
import { AudioSynthesisEarbuds2earbudsModel } from '@/module/audio-synthesis-earbuds2earbuds/audio-synthesis-earbuds2earbuds.model';
import { Cron } from '@nestjs/schedule';
import { EvaluationDataModel } from '../evaluation-data/evaluation-data.model';
import { EvaluationProjectModel } from '../evaluation-project/evaluation-project.model';
import { EvaluationModel } from '../evaluation/evaluation.model';

@Injectable()
export class AudioSynthesisService implements OnApplicationBootstrap {
  tempFilePath: string;
  countByServerParallel: number;
  running = false;
  useConv: boolean;

  constructor(
    @InjectRepository(EvaluationFileModel)
    private readonly evaluationFileModelRepository: Repository<EvaluationFileModel>,
    @InjectRepository(EvaluationDataModel)
    private readonly evaluationDataModelRepository: Repository<EvaluationDataModel>,
    @InjectRepository(EvaluationProjectModel)
    private readonly evaluationProjectModelRepository: Repository<EvaluationProjectModel>,
    @InjectRepository(EvaluationModel)
    private readonly evaluationModelRepository: Repository<EvaluationModel>,
    @InjectRepository(AudioSynthesisModel)
    private readonly audioSynthesisModelRepository: Repository<AudioSynthesisModel>,
    @InjectRepository(AudioSynthesisEarbuds2earbudsModel)
    private readonly audioSynthesisEarbuds2earbudsModelRepository: Repository<AudioSynthesisEarbuds2earbudsModel>,
    private readonly configService: ConfigService,
    private readonly fileService: FileService,
    private readonly loggerService: LoggerService,
  ) {
    this.tempFilePath = this.configService.get<string>('TEMP_FILE_PATH')!;
    this.countByServerParallel = Number(
      this.configService.get<string>('COUNT_BY_SERVER_PARALLEL')!,
    );
    this.useConv = this.configService.get<string>('USE_CONV') === '1'
  }

  onApplicationBootstrap() {
    this.cleanRunningTaskOnStart();
  }

  async cleanOssFile() {
    const clean = async () => {
      const cleanOssList = await this.audioSynthesisModelRepository.find({
        where: {
          createTime: LessThanOrEqual(
            dayjs()
              .subtract(AUDIO_SYNTHESIS_FILE_CLEAN_DAYS, 'd')
              .startOf('d')
              .format('YYYY-MM-DD HH:mm:ss') as unknown as number,
          ),
          targetAudioFile: Not(''),
        },
        take: 500,
        order: {
          createTime: 'ASC',
          id: 'ASC',
        },
      });
      if (cleanOssList.length) {
        this.loggerService.log(
          `此轮以下数据需要清除对应文件，共${cleanOssList.length}条数据`,
        );
        this.loggerService.log(
          cleanOssList.map((item) => {
            return {
              uuid: item.uuid,
              currentAudioFile: item.currentAudioFile,
              targetAudioFile: item.targetAudioFile,
              createTime: dayjs(item.createTime).format('YYYY-MM-DD HH:mm:ss'),
            };
          }),
        );
        const fileList = Array.from(
          new Set(
            cleanOssList
              .map((item) => [item.currentAudioFile, item.targetAudioFile])
              .flat(),
          ),
        ).filter((item) =>
          item.startsWith(`${FileGroup.TEMP_AUDIO_SYNTHESIS}/`),
        );
        this.loggerService.log('此轮oss清除以下文件');
        this.loggerService.log(fileList);
        const res = await this.fileService.batchRemoveFile(fileList);
        this.loggerService.log('此轮oss清除结果');
        this.loggerService.log(res);
        await this.audioSynthesisModelRepository.update(
          {
            uuid: In(cleanOssList.map((item) => item.uuid)),
          },
          {
            targetAudioFile: '',
          },
        );
        this.loggerService.log('此轮表数据修改完毕');
        await clean();
      } else {
        this.loggerService.log('已无需清理文件');
      }
    };

    this.loggerService.log('清理oss文件开始');
    await clean();
    this.loggerService.log('清理oss文件结束');
  }

  @Cron('01 00 * * *')
  async autoCleanOssFile() {
    if (
      process.env.NODE_APP_INSTANCE &&
      process.env.NODE_APP_INSTANCE !== '0'
    ) {
      return;
    }
    await this.cleanOssFile();
  }

  async indexEarbuds2Earbuds(
    query: AudioSynthesisIndexEarbuds2EarbudsDto,
    userUuid: string,
  ) {
    const queryBuilder = this.audioSynthesisModelRepository
      .createQueryBuilder('audioSynthesis')
      .leftJoinAndSelect(
        'audioSynthesis.audioSynthesisEarbuds2earbuds',
        'audioSynthesisEarbuds2earbuds',
      )
      .leftJoinAndSelect(
        'audioSynthesisEarbuds2earbuds.currentEvaluation',
        'currentEvaluation',
      )
      .leftJoinAndSelect(
        'audioSynthesisEarbuds2earbuds.targetEvaluation',
        'targetEvaluation',
      )
      .select([
        'audioSynthesis.uuid',
        'audioSynthesis.status',
        'audioSynthesis.currentName',
        'audioSynthesis.targetName',
        'audioSynthesis.currentAudioFile',
        'audioSynthesis.targetAudioFile',
        // 'audioSynthesis.errorReason',
        'audioSynthesis.createTime',
        'audioSynthesisEarbuds2earbuds.simulationAlgo',
        'audioSynthesisEarbuds2earbuds.id',
        'audioSynthesisEarbuds2earbuds.currentEvaluationUuid',
        'currentEvaluation.id',
        'currentEvaluation.buyUrl',
        'audioSynthesisEarbuds2earbuds.targetEvaluationUuid',
        'targetEvaluation.id',
        'targetEvaluation.buyUrl',
      ])
      .andWhere(`(audioSynthesis.userUuid = '${userUuid}')`);

    if (query.keyword) {
      queryBuilder.andWhere(
        '(audioSynthesis.currentName ILIKE :keyword OR audioSynthesis.targetName ILIKE :keyword OR audioSynthesis.currentAudioFile ILIKE :keyword)',
        {
          keyword: `%${query.keyword}%`,
        },
      );
    }

    query.wrapQueryBuilder(queryBuilder, 'audioSynthesis');

    return queryBuilder.getManyAndCount();
  }

  async batch(query: AudioSynthesisBatchDto, userUuid: string) {
    const queryBuilder = this.audioSynthesisModelRepository
      .createQueryBuilder('audioSynthesis')
      .select([
        'audioSynthesis.uuid',
        'audioSynthesis.status',
        'audioSynthesis.currentName',
        'audioSynthesis.targetName',
        'audioSynthesis.currentAudioFile',
        'audioSynthesis.targetAudioFile',
        // 'audioSynthesis.errorReason',
        'audioSynthesis.updateTime',
      ])
      .andWhere(`(audioSynthesis.userUuid = '${userUuid}')`);

    if (query.uuids) {
      queryBuilder.andWhere('(audioSynthesis.uuid IN (:...uuids))', {
        uuids: query.uuids.split(','),
      });
    }

    return queryBuilder.getMany();
  }

  async earbuds2Earbuds(body: Earbuds2EarbudsDto, userUuid: string) {
    await this.beforeValidate(body.files.length * body.targets.length, userUuid);

    const targetEvaluationUuidList = body.targets.map(target => target.evaluationUuid);

    const [currentEvalution, targetEvaluations] = await Promise.all([
      this.evaluationModelRepository.findOne({
        where: {
          uuid: body.currentEvaluationUuid,
        },
        relations: ['brand'],
      }),
      this.evaluationModelRepository.find({
        where: {
          uuid: In(targetEvaluationUuidList),
        },
        relations: ['brand'],
      }).then(res => {
        return body.targets.map(target=>{
          const current = res.find(item => item.uuid === target.evaluationUuid);
          return current ? {
            ...current,
            extra: {
              dataGroup: target.dataGroup,
            }
          } : undefined
        }).filter(item => item !== undefined);
      }),
    ]);

    if (!currentEvalution || targetEvaluations.length !== body.targets.length) {
      throw new BizFailedException(
        BizFailedCodeEnum.EVALUATION_NOT_EXISTS,
      );
    }

    let currentEvaluationFile: EvaluationFileModel | null = null;
    let targetEvaluationFiles: (EvaluationFileModel | null)[] = [];
    let targetEvaluationDatas: (EvaluationDataModel | null)[] = [];

    if ([SimulationAlgoType.TONING, SimulationAlgoType.TONING_DISTORTION].includes(body.simulationAlgo)) {
      [currentEvaluationFile, ...targetEvaluationFiles] = await Promise.all([
        this.evaluationFileModelRepository.findOne({
          where: {
            evaluationUuid: body.currentEvaluationUuid,
            group: body.currentDataGroup,
          },
        }),
        ...(body.targets.map(target => {
          return this.evaluationFileModelRepository.findOne({
            where: {
              evaluationUuid: target.evaluationUuid,
              group: target.dataGroup,
            },
          })
        }))
      ]);

      if (!currentEvaluationFile || targetEvaluationFiles.some(item => !item)) {
        throw new BizFailedException(
          BizFailedCodeEnum.EVALUATION_IMPULSERESPONSE_FILE_NOT_EXISTS,
        );
      }
    }

    if ([SimulationAlgoType.DISTORTION, SimulationAlgoType.TONING_DISTORTION].includes(body.simulationAlgo)) {
      const project = await this.evaluationProjectModelRepository.findOne({
        where: {
          code: 'THD'
        },
      });

      if (!project) {
        throw new BizFailedException(
          BizFailedCodeEnum.EVALUATION_THD_DATA_NOT_EXISTS,
        );
      }

      targetEvaluationDatas = await Promise.all(
        body.targets.map(target => {
          return this.evaluationDataModelRepository.findOne({
            where: {
              projectUuid: project.uuid,
              evaluationUuid: target.evaluationUuid,
              group: target.dataGroup,
            },
          })
        })
      );

      if (targetEvaluationDatas.some(item => !item)) {
        throw new BizFailedException(
          BizFailedCodeEnum.EVALUATION_THD_DATA_NOT_EXISTS,
        );
      }
    }

    const newTargetEvaluations = targetEvaluations.map(item => {
      return {
        ...item,
        extra: {
          dataGroup: item.extra.dataGroup,
          evaluationFile: targetEvaluationFiles.find(targetEvaluationFile => targetEvaluationFile!.evaluationUuid === item.uuid),
          evaluationData: targetEvaluationDatas.find(targetEvaluationData => targetEvaluationData!.evaluationUuid === item.uuid),
        }
      }
    });

    await this.saveAudioSynthesis({
      currentEvalution,
      currentDataGroup: body.currentDataGroup,
      currentEvaluationFile,

      newTargetEvaluations,

      files: body.files,

      userUuid,
      simulationAlgo: body.simulationAlgo,
    });

    this.checkWaitQueue();
  }

  @Transactional()
  private async saveAudioSynthesis({
    currentEvalution,
    currentDataGroup,
    currentEvaluationFile,

    newTargetEvaluations,

    files,
    userUuid,
    simulationAlgo,
  }: {
    currentEvalution: EvaluationModel;
    currentDataGroup: string;
    currentEvaluationFile: EvaluationFileModel | null;

    newTargetEvaluations: newTargetEvaluation[];

    files: string[],
    userUuid: string;
    simulationAlgo: SimulationAlgoType;
  }) {
    const cartesianArr: { file: string; newTargetEvaluation: newTargetEvaluation }[] = [];

    newTargetEvaluations.forEach(newTargetEvaluation => {
      files.forEach(file => {
        cartesianArr.push({
          newTargetEvaluation,
          file,
        })
      })
    })

    await Promise.all(
      cartesianArr.map((item) => {
        const filename = path.basename(item.file);
        const ext = path.extname(filename);
        return this.audioSynthesisModelRepository
          .save({
            currentName: `【${currentEvalution.brand.title}】${currentEvalution.title} - ${currentDataGroup}`,
            targetName: `【${item.newTargetEvaluation.brand.title}】${item.newTargetEvaluation.title} - ${item.newTargetEvaluation.extra.dataGroup}`,
            currentAudioFile: item.file,
            targetAudioFile: this.fileService.genKey(
              FileGroup.TEMP_AUDIO_SYNTHESIS,
              `${path.parse(filename).name} ${currentEvalution.title} 模拟 ${item.newTargetEvaluation.title}${ext}`,
            ),
            userUuid,
          })
          .then((res) => {
            return this.audioSynthesisEarbuds2earbudsModelRepository.save({
              audioSynthesisUuid: res.uuid,
              currentImpulseResponseFile: currentEvaluationFile?.url ?? '',
              targetImpulseResponseFile: item.newTargetEvaluation.extra.evaluationFile?.url ?? '',
              targetThdData: item.newTargetEvaluation.extra.evaluationData?.data ?? '',
              simulationAlgo: simulationAlgo,
              currentEvaluationUuid: currentEvalution.uuid,
              targetEvaluationUuid: item.newTargetEvaluation.uuid,
            });
          });
      }),
    );
  }

  async global(userUuid: string) {
    const [globalWorkList, userCountPerDay] = await Promise.all([
      this.audioSynthesisModelRepository.find({
        where: {
          status: Not(In(AUDIO_SYNTHESIS_FINALLY_STATUS_LIST)),
        },
        select: ['uuid', 'status', 'createTime'],
        order: {
          createTime: 'ASC',
          id: 'ASC',
        },
      }),
      this.countPerUserPerDay(userUuid),
    ]);

    return {
      globalWorkList,
      userCountPerDay,
      countByServerParallel: this.countByServerParallel,
    };
  }

  /* async batchCheckStatus(query: BatchCheckStatusDto,userUser: string){
    return this.audioSynthesisModelRepository.find({
      where: {
        uuid: In(query.uuids.split(',')),
      },
      select: ['uuid', 'status'],
    })
  }*/

  private async runEarbuds2Earbuds(
    uuid: string,
    userTempFileDir: string,
  ) {
    const currentLog = (
      message: string,
      type: 'log' | 'warn' | 'error' = 'log',
    ) => {
      this.loggerService[type](`生成任务${uuid}：${message}`);
    };

    const [audioSynthesis, audioSynthesisEarbuds2earbuds] = await Promise.all([
      this.audioSynthesisModelRepository.findOne({
        where: {
          uuid,
          status: AudioSynthesisStatus.WAITING,
        },
      }),
      this.audioSynthesisEarbuds2earbudsModelRepository.findOne({
        where: {
          audioSynthesisUuid: uuid,
        },
      }),
    ]);

    if (!audioSynthesis || !audioSynthesisEarbuds2earbuds) {
      return;
    }

    const filename = path.basename(audioSynthesis.currentAudioFile);
    const ext = path.extname(filename);
    const downloadPath = path.join(
      userTempFileDir,
      `input_${customAlphabet('0123456789', 12)()}${ext}`,
    );
    const outputAudioPath = path.join(
      userTempFileDir,
      `output_${customAlphabet('0123456789', 12)()}.wav`,
    );
    currentLog('任务元数据👇：');
    this.loggerService.log({
      userTempFileDir,
      filename,
      ext,
      downloadPath,
      outputAudioPath,
    });

    currentLog(`当前${this.useConv ? '开启' : '未开启'}算法计算`);

    try {
      fsExtra.ensureDirSync(userTempFileDir);

      const currentImpulseResponseFilePath = path.join(
        userTempFileDir,
        'current_impulse_response.wav',
      );
      const targetImpulseResponseFilePath = path.join(
        userTempFileDir,
        'target_impulse_response.wav',
      );
      const targetTHDDataFilePath = path.join(
        userTempFileDir,
        'target_thd_data.txt',
      );

      let updateResult = await this.audioSynthesisModelRepository.update(
        {
          uuid,
          status: AudioSynthesisStatus.WAITING,
        },
        {
          status:
            AudioSynthesisStatus.DOWNLOADING_IMPULSE_RESPONSE_FILE_FROM_OSS,
        },
      );
      if (updateResult.affected !== 1) {
        currentLog(`下载脉冲文件 状态出错，已停止`, 'error');
        return;
      }

      if ([SimulationAlgoType.TONING, SimulationAlgoType.TONING_DISTORTION].includes(audioSynthesisEarbuds2earbuds.simulationAlgo)) {
        currentLog(
          `开始下载脉冲文件：当前系统 ${currentImpulseResponseFilePath}, 目标系统 ${targetImpulseResponseFilePath}`,
        );

        await this.fileService.batchDownload({
          list: [
            {
              key: audioSynthesisEarbuds2earbuds.currentImpulseResponseFile,
              downloadPath: currentImpulseResponseFilePath,
            },
            {
              key: audioSynthesisEarbuds2earbuds.targetImpulseResponseFile,
              downloadPath: targetImpulseResponseFilePath,
            },
          ],
        });

        currentLog(`脉冲文件下载完成`);
      }


      if ([SimulationAlgoType.DISTORTION, SimulationAlgoType.TONING_DISTORTION].includes(audioSynthesisEarbuds2earbuds.simulationAlgo)) {
        currentLog(
          `开始生成THD数据：${targetTHDDataFilePath}`,
        );

        fsExtra.writeFileSync(targetTHDDataFilePath, audioSynthesisEarbuds2earbuds.targetThdData, 'utf-8');

        currentLog(`THD数据生成完成`);
      }


      currentLog('开始下载用户上传的源文件');

      updateResult = await this.audioSynthesisModelRepository.update(
        {
          uuid,
          status:
            AudioSynthesisStatus.DOWNLOADING_IMPULSE_RESPONSE_FILE_FROM_OSS,
        },
        {
          status: AudioSynthesisStatus.DOWNLOAD_USER_AUDIO_FILE_FROM_OSS,
        },
      );
      if (updateResult.affected !== 1) {
        currentLog(`下载用户上传的源文件 状态出错，已停止`, 'error');
        return;
      }
      await this.fileService.download({
        key: audioSynthesis.currentAudioFile,
        downloadPath: downloadPath,
      });

      currentLog('用户上传的源文件下载完成');

      currentLog('开始算法阶段');

      updateResult = await this.audioSynthesisModelRepository.update(
        {
          uuid,
          status: AudioSynthesisStatus.DOWNLOAD_USER_AUDIO_FILE_FROM_OSS,
        },
        {
          status: AudioSynthesisStatus.COMPUTING,
        },
      );
      if (updateResult.affected !== 1) {
        currentLog(`算法阶段 状态出错，已停止`, 'error');
        return;
      }

      const execCommand = ({
        commandType,
        inputPath,
        outputPath,
      }: {
        /** 命令类型： */
        commandType: 'TONING' | 'DISTORTION';
        /** 输入文件路径 */
        inputPath: string;
        /** 输出文件路径 */
        outputPath: string;
      }) => {
        const commandMap = {
          TONING: {
            command: `./Headphone_simulation.sh "${inputPath}" "${outputPath}" "${currentImpulseResponseFilePath}" "${targetImpulseResponseFilePath}"`,
            cwd: '/home/ntz/conv',
            env: {
              LD_LIBRARY_PATH:
                '/home/ntz/conv/libs/lib:/home/ntz/conv/libs/lib64:$LD_LIBRARY_PATH',
            },
          },
          DISTORTION: {
            command: `./run.sh "${inputPath}" "${outputPath}" "${targetTHDDataFilePath}"`,
            cwd: '/home/ntz/conv/harmonic',
            env: {
              LD_LIBRARY_PATH:
                '/home/ntz/conv/libs/lib:/home/ntz/conv/libs/lib64::/home/ntz/conv/harmonic/libs:/home/ntz/conv/libs/lib',
            },
          },
        };

        const command = commandMap[commandType];

        return new Promise((resolve, reject) => {
          currentLog(`在目录 ${command.cwd} 下执行命令：${command.command}`);

          if (this.useConv) {
            exec(
              command.command,
              {
                encoding: 'utf8',
                cwd: command.cwd,
                env: command.env,
              },
              (error, stdout, stderr) => {
                if (error) {
                  currentLog(
                    `Error executing command: ${error.message}`,
                    'error',
                  );
                  return reject(error);
                }

                if (stderr) {
                  currentLog(`Error in processing: ${stderr}`, 'error');
                  return reject(error);
                }

                currentLog(`Command output: ${stdout}`); // 调试信息

                currentLog(`音频生成完成：${outputPath}`);
                resolve(outputPath);
              },
            );
          } else {
            fsExtra.copyFileSync(inputPath, outputPath);
            currentLog(`音频生成完成：${outputPath}`);
            resolve(outputPath);
          }
        });
      };

      if (audioSynthesisEarbuds2earbuds.simulationAlgo === SimulationAlgoType.TONING) {
        await execCommand({
          commandType: SimulationAlgoType.TONING,
          inputPath: downloadPath,
          outputPath: outputAudioPath,
        });
      } else if (audioSynthesisEarbuds2earbuds.simulationAlgo === SimulationAlgoType.DISTORTION) {
        await execCommand({
          commandType: SimulationAlgoType.DISTORTION,
          inputPath: downloadPath,
          outputPath: outputAudioPath,
        });
      } else if (audioSynthesisEarbuds2earbuds.simulationAlgo === SimulationAlgoType.TONING_DISTORTION) {
        const middlePath = path.join(
          userTempFileDir,
          `middle_${customAlphabet('0123456789', 12)()}.wav`,
        );
        await execCommand({
          commandType: SimulationAlgoType.TONING,
          inputPath: downloadPath,
          outputPath: middlePath,
        });
        await execCommand({
          commandType: SimulationAlgoType.DISTORTION,
          inputPath: middlePath,
          outputPath: outputAudioPath,
        });
      }

      currentLog('算法阶段完成');

      currentLog(`即将上传生成的音频`);

      updateResult = await this.audioSynthesisModelRepository.update(
        {
          uuid,
          status: AudioSynthesisStatus.COMPUTING,
        },
        {
          status: AudioSynthesisStatus.UPLOADING_FINAL_FILE,
        },
      );
      if (updateResult.affected !== 1) {
        currentLog(`上传生成的音频 状态出错，已停止`, 'error');
        return;
      }

      await this.fileService.uploadLargeFile({
        key: audioSynthesis.targetAudioFile,
        filePath: outputAudioPath,
      });

      updateResult = await this.audioSynthesisModelRepository.update(
        {
          uuid,
          status: AudioSynthesisStatus.UPLOADING_FINAL_FILE,
        },
        {
          status: AudioSynthesisStatus.SUCCESS,
        },
      );
      if (updateResult.affected !== 1) {
        currentLog(`最终完成 状态出错，已停止`, 'error');
        return;
      }

      currentLog(`上传生成的音频完毕`);

      currentLog(`生成音频完毕，清理临时文件夹`);

      if (this.useConv) {
        fsExtra.removeSync(userTempFileDir);
      }

      currentLog(`临时文件夹清理完毕`);
    } catch (e) {
      await this.audioSynthesisModelRepository.update(
        {
          uuid,
        }, {
        status: AudioSynthesisStatus.FAIL,
        errorReason: e?.message ?? JSON.stringify(e)
      })
      currentLog(`生成音频失败，清理临时文件夹`);
      currentLog(e, 'error');
      if (this.useConv) {
        fsExtra.removeSync(userTempFileDir);
      }

      currentLog(`处理失败后临时文件夹清理完毕`);
    }
  }

  private async cleanRunningTaskOnStart() {
    await Promise.all([
      ...(this.useConv ? [fsExtra.emptyDir(path.join(this.tempFilePath, 'earbuds-2-earbuds'))] : []),
      this.audioSynthesisModelRepository.update(
        {
          status: Not(In(AUDIO_SYNTHESIS_FINALLY_STATUS_LIST)),
        },
        {
          status: AudioSynthesisStatus.TIMEOUT,
        },
      )
    ])
  }

  private async checkWaitQueue() {
    if (this.running) {
      return;
    }
    const waitingAudioSynthesis =
      await this.audioSynthesisModelRepository.findOne({
        where: {
          status: AudioSynthesisStatus.WAITING,
        },
        order: {
          createTime: 'ASC',
          id: 'ASC',
        },
      });
    if (waitingAudioSynthesis) {
      if (this.running) {
        // 再次判断拦截，免得查询后并发出错
        return;
      }
      this.running = true;

      const userTempFileDir = path.join(
        this.tempFilePath,
        'earbuds-2-earbuds',
        waitingAudioSynthesis.uuid,
      );

      const audioSynthesisEarbuds2earbuds = await this.audioSynthesisEarbuds2earbudsModelRepository.findOne({
        where: {
          audioSynthesisUuid: waitingAudioSynthesis.uuid,
        },
      })

      const timeoutMap = {
        [SimulationAlgoType.TONING]: 1000 * 60 * 2,
        [SimulationAlgoType.DISTORTION]: 1000 * 60 * 13,
        [SimulationAlgoType.TONING_DISTORTION]: 1000 * 60 * 15,
      }

      // 超时逻辑
      const timer = setTimeout(() => {
        this.audioSynthesisModelRepository.update(
          {
            uuid: waitingAudioSynthesis.uuid,
          },
          {
            status: AudioSynthesisStatus.TIMEOUT,
          },
        );
        if (this.useConv) {
          fsExtra.removeSync(userTempFileDir);
        }
      }, timeoutMap[audioSynthesisEarbuds2earbuds?.simulationAlgo ?? SimulationAlgoType.TONING]);

      await this.runEarbuds2Earbuds(waitingAudioSynthesis.uuid, userTempFileDir);

      clearTimeout(timer);

      this.running = false;

      this.checkWaitQueue();
    }
  }

  private countPerUserPerDay(userUuid: string) {
    return this.audioSynthesisModelRepository.count({
      where: {
        createTime: Between(
          dayjs()
            .startOf('d')
            .format('YYYY-MM-DD HH:mm:ss') as unknown as number,
          dayjs().endOf('d').format('YYYY-MM-DD HH:mm:ss') as unknown as number,
        ),
        userUuid,
        status: Not(
          In([AudioSynthesisStatus.FAIL, AudioSynthesisStatus.TIMEOUT]),
        ),
      },
    });
  }

  private async beforeValidate(addCount: number, userUuid: string) {
    const count = await this.countPerUserPerDay(userUuid);

    if (count + addCount > AUDIO_SYNTHESIS_COUNT_BY_PER_USER_PER_DAY) {
      throw new BizFailedException(
        BizFailedCodeEnum.AUDIO_SYNTHESIS_OVER_USER_COUNT,
      );
    }
  }
}


interface newTargetEvaluation extends EvaluationModel {
  extra: {
    dataGroup: string;
    evaluationFile?: EvaluationFileModel | null;
    evaluationData?: EvaluationDataModel | null;
  }
}